package com.ruoyi.common.gscUtil.StudyUtil.redisUtil.redisStrikeUtil;

//import com.google.common.hash.BloomFilter;
//import com.google.common.hash.Funnels;
//import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.ruoyi.common.core.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * https://cloud.tencent.com/developer/article/2089066?from_scene=register
 * redis的《***缓存穿透***》解决方案
 * 缓存穿透是指查询一个不存在的数据，导致请求直接落到数据库上，从而增加数据库的负载。 redis数据库都没有！
 * 总结
 * 1.布隆过滤器适用于过滤掉大量不存在的键。
 * 2.缓存空对象适用于处理少量不存在的键。
 * 3.互斥锁适用于高并发场景，防止多个线程同时查询数据库。
 * 4.限流与降级适用于极端情况，保护数据库不被压垮。
 * @author gsc
 * @date 2025/2/18
 */
public class redisStrikeUtil {
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private RedisCache redisCache;

    //1. 布隆过滤器（Bloom Filter）  ********隔壁有BloomFilterExample启动示例********
    //布隆过滤器是一种空间效率很高的数据结构，用于判断一个元素是否在一个集合中。它可以用来过滤掉那些肯定不存在于缓存中的请求。
    //实现步骤：
    //将所有可能的缓存键存储在布隆过滤器中。
    //在查询缓存之前，先通过布隆过滤器检查键是否存在。
    //如果布隆过滤器认为键不存在，则直接返回空结果，避免查询数据库。
//    private static BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 1000000, 0.01);
//    public void getBloomFilter(){
//        // 假设我们有一些缓存键
//        bloomFilter.put("key1");
//        bloomFilter.put("key2");
//
//        // 检查键是否存在
//        String key = "key3";
//        if (!bloomFilter.mightContain(key)) {
//            System.out.println("Key does not exist, return empty result");
//        } else {
//            // 查询缓存或数据库
//            System.out.println("Key might exist, proceed to query cache or DB");
//        }
//    }

    //2.缓存空对象（Cache Null Objects）
    //如果查询的数据在数据库中不存在，可以将一个空对象（如null或特殊标记）缓存起来，并设置一个较短的过期时间。
    //缓存空对象会有两个问题：
    //（1）value为null 不代表不占用内存空间，空值做了缓存，意味着缓存层中存了更多的键，需要更多的内存空间，比较有效的方法是针对这类数据设置一个较短的过期时间，让其自动剔除。
    //（2）缓存层和存储层的数据会有一段时间窗口的不一致，可能会对业务有一定影响。例如过期时间设置为5分钟，如果此时存储层添加了这个数据，
    //那此段时间就会出现缓存层和存储层数据的不一致，此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
    public void getCacheNullObjects(){
        String key = "non_existent_key";
        String value = redisCache.getCacheObject(key);
        if (value == null) {
            // 查询数据库
            value = queryDatabase(key);

            if (value == null) {
                // 缓存空对象，设置较短的过期时间
                redisCache.setCacheObject(key,  "NULL", 60, TimeUnit.SECONDS);// 60秒过期
            } else {
                redisCache.setCacheObject(key,  "NULL");
            }
        }
        if ("NULL".equals(value)) {
            System.out.println("Cache hit, but value is null");
        } else {
            System.out.println("Cache hit, value: " + value);
        }
    }
    private String queryDatabase(String key) {
        // 模拟数据库查询
        return null; // 假设数据库中不存在该键
    }


    //3.使用互斥锁（Mutex Lock）
    //当缓存失效时，使用互斥锁来确保只有一个线程去加载数据，其他线程等待。
    //这种方案思路比较简单，但是存在一定的隐患，如果在查询数据库 + 和 重建缓存（key失效后进行了大量的计算）时间过长，
    // 也可能会存在死锁和线程池阻塞的风险，高并发情景下吞吐量会大大降低！但是这种方法能够较好地降低后端存储负载，并在一致性上做得比较好
    public Object getMutexLock(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            // 缓存失效，尝试获取锁
            String lockKey = key + ":lock";
            boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS); //Absent:不存在返回true
            if (locked) {
                try {
                    // 从数据库加载数据
                    value = loadDataFromDB(key);
                    // 重新设置缓存
                    redisTemplate.opsForValue().set(key, value, 60, TimeUnit.MINUTES);
                } finally {
                    // 释放锁
                    redisTemplate.delete(lockKey);
                }
            } else {
                // 未获取到锁，等待一段时间后重试
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return getMutexLock(key); // 重试
            }
        }
        return value;
    }
    private Object loadDataFromDB(String key) {
        // 模拟从数据库加载数据
        return "data_from_db";
    }

    //4.使用限流和降级
    //在缓存失效时，使用限流和降级策略，避免大量请求直接打到数据库。可以使用Hystrix等工具实现限流和降级。
    // 使用Hystrix实现限流和降级
//    @HystrixCommand(fallbackMethod = "getDataFallback") //转向降级方法 ⬇ ⬇
    public String getData(String key) {
        String value = redisCache.getCacheObject(key);
        if (value == null) { // 缓存失效
            value = loadDataFromDB(key).toString(); // 从数据库加载数据
            redisCache.setCacheObject(key,value,60 * 60,TimeUnit.SECONDS);// 设置缓存
        }
        return value;
    }

    public String getDataFallback(String key) {//降级处理
        return "fallback value"; //  降级逻辑，例如返回默认值、缓存数据或提示信息等
    }


}
